[C 기술면접] 추가 빈출 질문 모음
NOTE
앞선 10개 질문 외에, C/시스템 프로그래밍 기술면접에서 자주 나오는 질문을 영역별로 정리했다. 각 항목은 한두 문장으로 먼저 답하고, 꼬리 질문이 나오면 아래 근거를 덧붙이는 식으로 외운다. 이미 정리된 개별 노트가 있으면 링크를 달았다.
1. 포인터 · 배열 · 메모리
| 질문 | 답변 요지 |
|---|---|
| 포인터와 배열의 차이? | 배열은 연속된 메모리 블록(이름은 첫 원소 주소로 decay), 포인터는 주소를 담는 변수. sizeof(arr)은 전체 크기, sizeof(ptr)은 포인터 크기. 배열 이름은 대입 불가(수정 불가능한 lvalue). 함수 인자로 넘기면 포인터로 바뀌므로 길이를 따로 전달해야 한다. |
const char *p, char * const p, const char * const p? | 각각 가리키는 값이 상수 / 포인터 자체가 상수 / 둘 다 상수. 읽는 법: * 기준 왼쪽이 값, 오른쪽이 포인터. (포인터 상수성 정리) |
void *란? | 타입 없는 포인터. 모든 객체 포인터를 암시 변환 가능(C), 역참조·산술 불가. malloc의 반환형. C++과 달리 C에서는 캐스팅 불필요. |
malloc / calloc / realloc / free 차이? | malloc: 초기화 안 된 메모리. calloc(n, size): 0 초기화 + 곱셈 오버플로우 검사. realloc: 크기 변경(이동 가능) — 실패 시 NULL이고 원래 블록은 유지되므로 p = realloc(p, n)은 누수 위험 → 임시 포인터에 받아 확인. free: 해제(free(NULL)은 안전). |
| 메모리 누수 / 댕글링 / 이중 해제? | 누수: 해제 안 함. 댕글링: 해제된 메모리를 가리키는 포인터 사용. 이중 해제: 같은 블록 두 번 free → 힙 손상. 대응: 소유권 규칙, free 후 NULL 대입, 생성/소멸 쌍, ASan/Valgrind. (실무 C 관례 B2~B4) |
| 스택과 힙 차이? | 스택: 함수 호출 프레임(지역 변수, 자동 해제, 빠름, 크기 제한 ~8MB, 스레드별). 힙: 동적 할당(수명 수동 관리, 단편화, 느림, 크기 큼). 프로세스 메모리 레이아웃: 텍스트(코드) / 데이터(.data) / BSS(.bss) / 힙(위로 성장) / mmap 영역 / 스택(아래로 성장). |
| 지역 변수의 주소를 반환하면? | 함수 종료 시 스택 프레임이 사라져 댕글링 포인터가 된다(UB). 힙 할당하거나 호출자 버퍼를 인자로 받는다. |
| 메모리 정렬(alignment)과 구조체 패딩? | CPU가 타입 크기의 배수 주소에서 효율적으로 읽도록 컴파일러가 멤버 사이에 패딩을 넣는다. 크기는 sizeof로 확인, 큰 멤버부터 선언하면 줄어든다. #pragma pack/packed는 패딩 제거(성능·정렬 문제 주의). (구조체 패딩과 멤버 정렬) |
struct와 union 차이? | struct: 멤버가 각자 공간(합계), union: 멤버가 같은 공간 공유(가장 큰 멤버 크기). union은 한 번에 하나만 유효, 타입 퍼닝/프로토콜 해석에 사용. (Union, Typedef, Struct) |
| 엔디안이란? | 멀티바이트 정수의 바이트 저장 순서. 리틀 엔디안(x86/ARM 기본): 낮은 바이트가 낮은 주소. 네트워크는 빅 엔디안 → htons/htonl, ntohs/ntohl로 변환. (엔디안과 htons) |
| 함수 포인터란 / 어디에 쓰나? | 함수의 주소를 담는 포인터. 콜백, 디스패치 테이블, 플러그인, 전략 패턴, qsort 비교 함수. int (*fp)(int, int); (함수 포인터와 디스패치 테이블) |
restrict란? | 같은 메모리를 다른 포인터로 접근하지 않겠다는 약속 → 컴파일러 최적화 허용. 어기면 UB. memcpy(겹치면 안 됨) vs memmove(겹쳐도 됨)의 차이도 같은 맥락. |
memcpy vs memmove vs memcmp? | memcpy: 영역 겹침 금지. memmove: 겹쳐도 안전. memcmp: 바이트 비교(구조체 패딩 바이트 때문에 구조체 비교에 부적합). |
2. 타입 · 연산 · 문법
| 질문 | 답변 요지 |
|---|---|
sizeof는 함수인가? | 연산자(컴파일 타임, VLA 제외). 인자의 표현식은 평가되지 않는다. sizeof(int)와 sizeof arr 형태. 반환형 size_t는 %zu로 출력. |
| signed/unsigned 비교 함정? | 비교 시 signed가 unsigned로 변환되어 -1 < 1u가 거짓. size_t 루프에서 i >= 0은 항상 참(무한 루프). |
| 정수 오버플로우? | signed 오버플로우는 UB(최적화로 예상 못 한 결과), unsigned는 모듈러 wrap. 연산 전 범위 검사. (정수·타입변환 계열) |
| 정수 승격(integer promotion)? | char/short가 연산 시 int로 승격. char c = 0xFF; c << 8 같은 곳에서 부호 확장·예상 밖 결과. |
==와 = 실수, 비트 연산자 우선순위? | if (a & b == c)는 a & (b == c) — ==가 &보다 우선. 괄호 사용. |
i++ + ++i 같은 식? | 같은 객체를 시퀀스 포인트 없이 두 번 수정 → UB. 인자 평가 순서도 미정의. |
static 키워드의 세 가지 용도? | ① 지역 static: 호출 간 값 유지(수명 전체, 스코프 함수) ② 전역/함수 static: 파일 내부 링키지(은닉) ③ (C++) 클래스 멤버. (extern과 static) |
volatile은 무엇을 위한가? | 컴파일러가 해당 변수 접근을 최적화로 생략/재배치하지 못하게(메모리 매핑 I/O, 시그널 핸들러 변수). 스레드 동기화·원자성 보장 아님. |
const의 의미와 #define과의 차이? | 읽기 전용 변수(타입·스코프 있음). 매크로는 단순 치환. |
| 선언(declaration)과 정의(definition)? | 선언은 이름/타입을 알림(extern int x;), 정의는 실제 저장 공간 할당(int x;). 정의는 하나, 선언은 여러 번 가능(ODR). |
typedef와 #define의 차이? | typedef는 타입 별칭(컴파일러가 처리, 포인터 타입에서 안전), #define은 텍스트 치환(#define PINT int*; PINT a, b;는 b가 int). |
| 비트 연산 활용? | 플래그 설정/해제/토글/검사: ` |
| 정의되지 않은 동작(UB) / 구현 정의 / 지정되지 않은 동작? | UB: 표준이 아무 요구도 안 함(최적화 가정이 깨져 임의 결과). 구현 정의: 컴파일러가 문서화해야 함(int 크기, 시프트). 지정되지 않음: 여러 합법적 결과 중 하나(인자 평가 순서). UBSan으로 탐지. |
3. 문자열
| 질문 | 답변 요지 |
|---|---|
strcpy / strncpy / snprintf / strlcpy 차이? | strcpy: 길이 검사 없음(오버플로우). strncpy: n바이트 복사, 널 종단 보장 안 함, 남는 공간은 0으로 채움(성능 낭비). snprintf(buf, sizeof buf, "%s", src): 항상 널 종단, 가장 무난. strlcpy: 널 종단 보장(BSD/glibc 2.38+). (문자열 복사 비교) |
| 문자열 리터럴 수정이 안 되는 이유? | char *s = "abc"는 .rodata(읽기 전용)를 가리킨다. 수정하려면 char s[] = "abc"(스택에 복사본). |
strlen과 sizeof 차이? | strlen은 널 문자 전까지 런타임 계산, sizeof는 배열 전체 크기(널 포함, 컴파일 타임). 포인터에 sizeof는 포인터 크기. |
strtok의 문제? | 내부 정적 상태 사용 → 스레드 안전하지 않고 원본 문자열을 수정. strtok_r 사용. (strtok_r과 sscanf) |
| 버퍼 오버플로우를 막는 방법? | 길이 있는 함수 사용(snprintf, fgets), 경계 검사, -fstack-protector, ASan, 입력 검증. (메모리 경계 계열) |
| 포맷 스트링 취약점? | printf(user_input)처럼 사용자 입력을 포맷으로 쓰면 %n, %x로 메모리 읽기/쓰기 가능 → 항상 printf("%s", user_input). |
4. 프로세스 · 시그널 · 스레드
| 질문 | 답변 요지 |
|---|---|
| 프로세스와 스레드 차이? | 프로세스: 독립된 주소 공간·자원, 생성/문맥 전환 비용 큼, 격리 강함(IPC 필요). 스레드: 프로세스 내에서 힙·전역·파일 디스크립터 공유, 스택/레지스터만 독립, 가볍지만 한 스레드 크래시가 전체에 영향. (스레드 기본개념, IPC와 스레드 메모리 공유 비교) |
fork() 동작? | 현재 프로세스를 복제(copy-on-write). 반환값: 부모는 자식 PID, 자식은 0, 실패 -1. 자식은 fd를 상속하며 락/스레드 상태에 주의(멀티스레드에서 fork 후에는 async-signal-safe 함수만 안전, 보통 바로 exec). |
fork vs exec? | fork: 복제, exec: 현재 프로세스 이미지를 새 프로그램으로 교체(PID 유지). 셸이 명령을 실행하는 방식 = fork + exec + wait. |
| 좀비 / 고아 프로세스? | 좀비: 종료됐지만 부모가 wait하지 않아 프로세스 테이블에 남은 상태. 고아: 부모가 먼저 종료돼 init(1)이 입양. 좀비 방지: waitpid, SIGCHLD 핸들러, SA_NOCLDWAIT. (좀비 프로세스(fork·waitpid)) |
| 시그널 핸들러에서 주의할 점? | async-signal-safe 함수만 호출(printf, malloc 금지). 공유 변수는 volatile sig_atomic_t. 핸들러는 플래그만 세우고 메인 루프에서 처리. signal() 대신 sigaction(). |
SIGKILL/SIGSTOP은 왜 못 잡나? | 커널이 프로세스를 항상 제어할 수 있어야 하므로 핸들러 등록/무시/차단 불가. |
SIGPIPE란? | 닫힌 소켓/파이프에 write하면 발생해 프로세스가 종료. 서버는 signal(SIGPIPE, SIG_IGN) 또는 send(MSG_NOSIGNAL)로 EPIPE를 받게 처리. |
| 재진입(reentrant) 함수란? | 여러 흐름(스레드/시그널)이 동시에 호출해도 안전한 함수(전역/정적 상태 없음). strtok→strtok_r, localtime→localtime_r. |
| 스레드 안전(thread-safe)과 재진입의 차이? | 재진입 ⊂ 스레드 안전. 락으로 스레드 안전하게 만든 함수는 시그널 핸들러에서 재진입하면 데드락될 수 있다. |
pthread_join / detach? | join: 스레드 종료 대기 + 자원 회수(안 하면 좀비 스레드 누수). detach: 종료 시 자동 회수(반환값 못 받음). |
스레드 간 데이터 전달 시 pthread_create 인자 주의점? | 지역 변수 주소를 넘기면 함수 종료 후 댕글링 → 힙에 할당한 구조체 전달, 소유권 정의. (pthread_create에 구조체 포인터 넘기기) |
| 데드락 / 레이스 컨디션? | 데드락: 서로의 락을 기다려 진행 불가(4조건, 락 순서 고정으로 예방). 레이스: 실행 순서에 따라 결과가 달라지는 동기화 부재. (동기화 노트) |
errno는 스레드 안전한가? | 스레드별(thread-local)이다. 하지만 실패한 호출 직후 바로 확인해야 한다(다음 호출이 덮어씀). |
| 문맥 교환(context switch) 비용? | 레지스터·스택·페이지 테이블(프로세스 간) 교체, TLB/캐시 오염. 스레드 간이 프로세스 간보다 싸다. |
| 가상 메모리란? | 프로세스마다 독립된 가상 주소 공간을 페이지 단위로 물리 메모리에 매핑(MMU/TLB). 격리·보호, 필요 시 적재(demand paging), 공유(공유 라이브러리, mmap). |
5. 네트워크 · I/O
| 질문 | 답변 요지 |
|---|---|
| TCP 소켓 서버 흐름? | 서버: socket → bind → listen → accept → read/write → close. 클라이언트: socket → connect → read/write → close. (Socket 통신) |
| TCP vs UDP? | TCP: 연결 지향, 신뢰성(재전송·순서·흐름/혼잡 제어), 바이트 스트림. UDP: 비연결, 비신뢰, 메시지(데이터그램) 경계 보존, 낮은 지연. |
| TCP는 메시지 경계가 없다는 뜻? | send 두 번이 recv 한 번에 붙거나, 한 번이 두 번에 쪼개져 올 수 있다. 길이 헤더/구분자로 프레이밍하고 recv를 루프로 처리. |
read/write가 요청한 만큼 안 읽고 쓰면? | 부분 읽기/쓰기가 정상이다. 루프로 남은 양을 처리하고 EINTR(시그널로 중단)은 재시도, EAGAIN(논블로킹)은 나중에. |
| 3-way / 4-way handshake? | 연결: SYN → SYN+ACK → ACK. 종료: FIN → ACK → FIN → ACK(양방향 독립). TIME_WAIT는 마지막 ACK 유실 대비로 능동 종료 측에 남는다. |
TIME_WAIT가 많이 쌓이면? | 짧은 연결을 많이 맺고 끊을 때. SO_REUSEADDR, 커넥션 재사용(Keep-Alive), 서버 쪽이 아닌 클라이언트가 먼저 닫게 설계. |
SO_REUSEADDR을 쓰는 이유? | 서버 재시작 시 TIME_WAIT 포트에 bind가 Address already in use로 실패하는 것을 방지. |
| 블로킹/논블로킹 소켓? | 블로킹은 준비될 때까지 잠듦, 논블로킹은 즉시 EAGAIN 반환. 다중화는 fd 다중화 노트. |
stdio 버퍼링? | printf는 라인 버퍼(터미널)/풀 버퍼(파일·파이프). 크래시 시 버퍼 내용 유실 → fflush, setvbuf, 디버그는 stderr(비버퍼). (파일 I/O 구조와 버퍼링) |
파일 디스크립터와 FILE* 차이? | fd: 커널 수준 정수 핸들(open/read/write). FILE*: 사용자 공간 버퍼를 가진 C 표준 라이브러리 추상화(fopen/fread). |
| 파일 디스크립터 누수의 증상? | Too many open files(EMFILE). ulimit -n, lsof -p로 확인, close 누락/예외 경로 점검. |
6. 빌드 · 링크 · 도구
| 질문 | 답변 요지 |
|---|---|
| C 컴파일 4단계? | 전처리(-E) → 컴파일(-S, 어셈블리) → 어셈블(-c, 오브젝트) → 링크(실행 파일). |
| 정적 라이브러리 vs 동적 라이브러리? | 정적(.a): 링크 시 코드가 실행 파일에 포함(배포 단순, 크기 큼). 동적(.so): 실행 시 로드(공유로 메모리 절약, 업데이트 용이, 실행 환경에 의존 — LD_LIBRARY_PATH, rpath, ldd로 확인). |
undefined reference vs multiple definition? | 선언만 있고 정의가 없음(라이브러리 링크 누락/순서) vs 정의가 둘 이상(헤더에 정의, 전역 중복). 정적 라이브러리는 링크 순서가 중요(의존하는 쪽이 앞). (정적 라이브러리 링크 순서와 심볼 추적) |
Makefile 기본 개념? | 타깃·의존성·레시피, 타임스탬프 기반 증분 빌드, 변수/패턴 규칙, .PHONY. |
| 컴파일 경고 옵션? | -Wall -Wextra -Werror, 디버그 -g -O0, 릴리스 -O2, 보안 -fstack-protector-strong -D_FORTIFY_SOURCE=2 -fPIE -pie. |
| 헤더 가드가 필요한 이유? | 같은 헤더의 중복 포함으로 인한 재정의 오류 방지(#ifndef/#pragma once). |
| ABI와 API 차이? | API: 소스 수준 인터페이스. ABI: 바이너리 수준(호출 규약, 구조체 레이아웃, 심볼). 구조체 레이아웃을 바꾸면 ABI가 깨진다. |
| 정적 분석 / 런타임 검사 도구? | 컴파일러 경고, clang-tidy/cppcheck/Coverity(정적), ASan/UBSan/TSan/Valgrind(동적), gdb. |
gdb 기본 명령? | break, run, next/step, bt, print, watch, x, info locals, continue. (Segfault 디버깅 노트) |
7. 설계 · 품질 · 실무 경험
| 질문 | 답변 요지 |
|---|---|
| 에러 처리는 어떻게 하나? | 반환 코드(0 성공 / 음수 실패) + errno, 호출 직후 반드시 검사, 실패 시 지금까지 확보한 자원을 역순으로 정리(goto cleanup 단일 출구). 에러 메시지에 strerror(errno). (실무 C 관례 A2, A14) |
goto는 쓰면 안 되나? | 루프 대용·임의 점프는 금기지만, 다중 자원 정리를 위한 단일 출구(cleanup) 용도는 Linux 커널·curl에서도 쓰는 관례. |
| 모듈화 / 캡슐화를 C에서는? | 헤더(공개 API)와 소스 분리, 내부는 static, 불투명 포인터, 모듈 접두어(네임스페이스 흉내). (설계와 메모리 기본) |
| 코드 리뷰에서 무엇을 보나? | 반환값 검사, 자원 해제 경로, 버퍼 경계, 정수 오버플로우, 스레드 안전성, 락 순서, 매직 넘버, 의미 있는 이름, 테스트 가능성. |
| 시큐어 코딩 핵심? | 입력 검증, 경계 검사, 안전한 문자열 함수, 정수 오버플로우 방지, 최소 권한, 하드코딩된 비밀 금지, 에러 정보 노출 제한. (시큐어코딩 개요) |
| 성능 최적화는 어떻게 접근하나? | 측정 먼저(perf, gprof, valgrind --tool=callgrind) → 병목 파악 → 알고리즘/자료구조 → 메모리 접근 패턴(캐시 지역성) → 락 경합 → 컴파일러 최적화. 추측 최적화 금지. |
| 캐시 지역성(locality)과 false sharing? | 연속 메모리 접근이 캐시 적중률을 높인다(배열 > 연결 리스트). 서로 다른 스레드가 쓰는 변수가 같은 캐시 라인(64B)에 있으면 서로 무효화시켜 느려짐 → 패딩/정렬. |
| 데몬 프로세스 작성 요령? | fork, setsid, 작업 디렉터리/umask 설정, 표준 fd 닫기(또는 /dev/null), 시그널(SIGTERM 종료/SIGHUP 재로드) 처리, PID 파일, 로그. 현대에는 systemd 서비스로 포그라운드 실행이 보통. |
| 자신 있는 경험을 묻는다면? | STAR(상황-과제-행동-결과)로: 장애 원인 추적(core/ASan), 성능 개선 수치, 동시성 문제 해결, 설계 트레이드오프를 구체적 수치와 함께. |
면접 준비 팁
- “왜”를 설명한다: 정의만 말하지 말고 이유와 트레이드오프까지(예: spinlock은 짧은 구간에서 문맥 전환 비용을 피하려고).
- 코드를 손으로 쓸 수 있게: 연결 리스트 뒤집기, 링 버퍼/큐,
strcpy/strlen/memcpy/atoi구현, 비트 연산(카운트/반전), 소켓 서버 골격은 화이트보드에서 바로 쓸 수 있어야 한다. - 경계 조건을 말한다: NULL, 빈 입력, 오버플로우, 에러 경로, 동시성을 먼저 언급하면 깊이가 드러난다.
- 모르면 추론하고 단정하지 않는다: 아는 원리에서 출발해 가능성을 말하고, 확인 방법(도구)을 함께 제시한다.